/*************************************************************************
 *
 * SerialTask
 * 
 * (c) Ales Povalac, alpov@alpov.net, http://www.alpov.net/
 * 
 *************************************************************************
 *
 * $Id: serial.c 83 2006-08-11 09:25:54Z alpov $
 * 
 *************************************************************************/         

#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/crc16.h>
#include "../avrx/avrx.h"
#include "../avrx/AvrXFifo.h"
#include "serial.h"
#include "serial_hnds.h"
#include "data.h"
#include "menu.h"
#include "locate.h"

MessageQueue RxTxQueue, AckQueue;
MessageControlBlock RxReady, AckReady;
TimerMessageBlock RxTimeout, AckTimeout;
Mutex SendMutex, STDATA_Written, RWDATA_Written;
static unsigned char ack_seq;

AVRX_DECL_FIFO(TxFifo, PACKET_TX_BUF_SIZE);


/*************************************************************************
 * vypocet CRC16 - zapouzdreni inline funkce
 *************************************************************************/ 
void crc16_update(int *crc, char c)
{
    *crc = _crc16_update(*crc, c);
}


/*************************************************************************
 * obsluha preruseni prijateho bajtu USART_RXC_vect
 *************************************************************************/ 
AVRX_SIGINT(USART_RXC_vect)
{
    IntProlog();
    UCSRB &= ~_BV(RXCIE);
    EndCritical();
    AvrXIntSendMessage(&RxTxQueue, &RxReady);
    Epilog();
}


/*************************************************************************
 * obsluha preruseni odeslaneho bajtu USART_TXC_vect
 *************************************************************************/ 
AVRX_SIGINT(USART_TXC_vect)
{
    IntProlog();
    SerialSwitchRx();                           // jdeme na prijem
    Epilog();
}


/*************************************************************************
 * obsluha preruseni prazdneho datoveho registru USART_UDRE_vect
 *************************************************************************/ 
AVRX_SIGINT(USART_UDRE_vect)
{
    IntProlog();
    UCSRB &= ~_BV(UDRIE);
    EndCritical();
    
    int c = AvrXPullFifo(TxFifo);
    
    if (c >= 0) {
        SerialSwitchTx();                       // jdeme na vysilani
        UDR = c;
        UCSRB |= _BV(UDRIE);
    }
    
    Epilog();
}


/*************************************************************************
 * precteni bajtu
 *************************************************************************/ 
int SerialRx(void)
{
    unsigned char c;
    MessageControlBlock *p;
    
    AvrXStartTimerMessage(&RxTimeout, PACKET_RX_TIMEOUT, &RxTxQueue);
    p = AvrXWaitMessage(&RxTxQueue);
    
    if (p == &RxReady) {
        AvrXCancelTimerMessage(&RxTimeout, &RxTxQueue);
        c = UDR;
        UCSRB |= _BV(RXCIE);
        return c;

    } else {
        return -1;
    }
}


/*************************************************************************
 * uloha SerialTask
 * min. stack >70B FIXME 
 *************************************************************************/ 
AVRX_GCC_TASKDEF(SerialTask, 90, 1)
{
    static PACKET_HEADER ph;
    static char data[PACKET_RX_BUF_SIZE];
    PACKET_FOOTER pf;
    int length;
    int i, crc, c;
    
    while (1) {
        if (SerialRx() != PACKET_PREFIX)
            continue;                               // MSB prefixu, timeout
        
        if (SerialRx() != PACKET_PREFIX)
            continue;                               // LSB prefixu, timeout
        
        memset(&data, 0, sizeof(data));

        length = SerialRx();
        if ((length > PACKET_RX_MAX_LENGTH) || (length < sizeof(PACKET_HEADER)))
            continue;                               // delsi nez buffer nebo kratsi nez hlavicka, timeout
        
        length -= sizeof(PACKET_HEADER);
        crc = 0xffff;
        
        for (i = 0; i < sizeof(PACKET_HEADER); i++) {
            c = SerialRx();
            if (c < 0)
                break;
            ph.raw_data[i] = c;
            crc16_update(&crc, c);
        }
        if (c < 0)
            continue;                               // timeout
        
        for (i = 0; i < length; i++) {
            c = SerialRx();
            if (c < 0)
                break;
            data[i] = c;
            crc16_update(&crc, c);
        }
        if (c < 0)
            continue;                               // timeout
        
        for (i = 0; i < sizeof(PACKET_FOOTER); i++) {
            c = SerialRx();
            if (c < 0)
                break;
            pf.raw_data[i] = c;
        }
        if (c < 0)
            continue;                               // timeout

        if (crc != pf.crc)
            continue;                               // chybne CRC
        
        if ((ph.dest != ADDR_TERMINAL) && (ph.dest != ADDR_BROADCAST))
            continue;                               // nesouhlasi adresa
        
        // ---- handlery ----
        
        if (ph.cmd == CMD_DESC_WRITE) {
            DESC_W_Handler(&ph, data, length);
        }
        else if (ph.cmd == CMD_DESC_READ) {
            DESC_R_Handler(&ph);
        }
        else if (ph.cmd == CMD_STDATA_WRITE) {
            STDATA_W_Handler(&ph, data, length);
        }
        else if (ph.cmd == CMD_RWDATA_WRITE) {
            RWDATA_W_Handler(&ph, data, length);
        }
        else if (ph.cmd == CMD_RWDATA_READ) {
            RWDATA_R_Handler(&ph);
        }
        else if (ph.cmd == CMD_ACKNOWLEDGE) {
            ack_seq = ph.seq;
            AvrXSendMessage(&AckQueue, &AckReady);
        }
    }
}


/*************************************************************************
 * odeslani datoveho paketu
 *************************************************************************/ 
void SerialSendPacket(PACKET_HEADER *ph, char *data, unsigned char length, char gen_seq)
{
    static unsigned char sequence = 0;
    int i, crc = 0xffff;
    PACKET_FOOTER pf;
    
    if (length > PACKET_TX_MAX_LENGTH)
        return;
    
    AvrXWaitSemaphore(&SendMutex);
    
    while (AvrXStatFifo(TxFifo))                // cekej, dokud neodesle vse
        AvrXWaitSemaphore(&TxFifo->Consumer);
    
    AvrXPutFifo(TxFifo, PACKET_PREFIX);
    AvrXPutFifo(TxFifo, PACKET_PREFIX);
    AvrXPutFifo(TxFifo, sizeof(PACKET_HEADER) + length);
    
    if (gen_seq)
        ph->seq = sequence++;
    
    for (i = 0; i < sizeof(PACKET_HEADER); i++) {
        crc16_update(&crc, ph->raw_data[i]);
        AvrXPutFifo(TxFifo, ph->raw_data[i]);
    }
    
    for (i = 0; i < length; i++) {
        crc16_update(&crc, *data);
        AvrXPutFifo(TxFifo, *data);
        data++;
    }
    
    pf.crc = crc;
    for (i = 0; i < sizeof(PACKET_FOOTER); i++) {
        AvrXPutFifo(TxFifo, pf.raw_data[i]);
    }
    
    UCSRB |= _BV(UDRIE);    // zahaj vysilani
    
    AvrXSetSemaphore(&SendMutex);
}


/*************************************************************************
 * odeslani potvrzeni prijateho paketu
 *************************************************************************/ 
void SerialSendAck(PACKET_HEADER *ph)
{
    PACKET_HEADER ph_ack;
    
    ph_ack.seq = ph->seq;
    ph_ack.dest = ph->src;
    ph_ack.src = ADDR_TERMINAL;
    ph_ack.cmd = CMD_ACKNOWLEDGE;
    ph_ack.item = 0;
    
    SerialSendPacket(&ph_ack, NULL, 0, 0);
}


/*************************************************************************
 * testuje, zda bylo obdrzeno potvrzeni
 * nesmi byt volana ze SerialTask 
 *************************************************************************/ 
int SerialWaitAck(unsigned char seq)
{
    MessageControlBlock *p;
    
    if (AvrXSelf() == PID(SerialTask))
        return -1;
    
    AvrXStartTimerMessage(&AckTimeout, PACKET_RX_TIMEOUT, &AckQueue);
    p = AvrXWaitMessage(&AckQueue);
    AvrXCancelTimerMessage(&AckTimeout, &AckQueue);
    return ((p == &AckReady) && (ack_seq == seq)) ? 1 : 0;
}


/*************************************************************************
 * odeslani udaju, vyuziva RWDATA_R_Handler()
 * funkce provedeni updatu volana z menu 
 *************************************************************************/ 
void Serial_RWDATA_W(unsigned char item)
{
    PACKET_HEADER ph;
    TimerControlBlock timer;
    
    timer.semaphore = SEM_PEND;
    
    ph.src = ADDR_SERVER; // handler -> dest=src
    ph.item_hi = item;
    ph.item_lo = 0;
    
    for (int i = 0; i < PACKET_TX_TRIES; i++) {
        RWDATA_R_Handler(&ph);
        if (SerialWaitAck(ph.seq))
            return;
        AvrXDelay(&timer, PACKET_ACK_DELAY);
    }
    MenuMessage(MSG_COMERR);
}


/*************************************************************************
 * zadost o zaslani RWDATA udaju, vyuzivano pri startu 
 *************************************************************************/ 
void Serial_RWDATA_R(void)
{
    PACKET_HEADER ph;
    
    ph.dest = ADDR_SERVER;
    ph.src = ADDR_TERMINAL;
    ph.cmd = CMD_RWDATA_READ;
    ph.item_hi = ITEM_ALL;
    ph.item_lo = 0;
    
    SerialSendPacket(&ph, NULL, 0, 1);
}


/*************************************************************************
 * zadost o zaslani STDATA udaju, vyuzivano pri startu 
 *************************************************************************/ 
void Serial_STDATA_R(void)
{
    PACKET_HEADER ph;
    
    ph.dest = ADDR_SERVER;
    ph.src = ADDR_TERMINAL;
    ph.cmd = CMD_STDATA_READ;
    ph.item_hi = ITEM_ALL;
    ph.item_lo = 0;
    
    SerialSendPacket(&ph, NULL, 0, 1);
}


/*************************************************************************
 * inicializace tasku
 *************************************************************************/ 
void InitSerialTask(void)
{
    unsigned int ubrr = 12;
    
//  memset(SerialTaskStk, 0xA0A0, sizeof(SerialTaskStk));

    AVRX_INIT_FIFO(TxFifo);
    
    DDRD |= _BV(PD2);
    SerialSwitchRx();
    
	UBRRL = (unsigned char)(ubrr);
	UBRRH = (unsigned char)(ubrr >> 8);
	UCSRA = 0;
    UCSRB = _BV(RXCIE) | _BV(TXCIE) | _BV(RXEN) | _BV(TXEN);
	UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);
	
	AvrXSetSemaphore(&SendMutex);
}

